<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgl - multiple elements with text</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<link type="text/css" rel="stylesheet" href="main.css">
		<style>
			* {
				box-sizing: border-box;
				-moz-box-sizing: border-box;
			}

			body {
				background-color: #fff;
				color: #444;
				margin: auto;
				padding: .5in;
				max-width: 7in;
				text-align: justify;
			}

			a {
				color: #08f;
			}

			#info {
				left: 0px;
			}

			.view {
				width: 5in;
				height: 5in;
				margin: auto;
			}

			#c {
				position: fixed;
				left: 0px; top: 0px;
				width: 100%;
				height: 100%;
				background-color: #fff;
				z-index: -1;
			}

			.math {
				text-align: center;
			}

			.math-frac {
				display: inline-block;
				vertical-align: middle;
			}

			.math-num {
				display: block;
			}

			.math-denom {
				display: block;
				border-top: 1px solid;
			}

			.math-sqrt {
				display: inline-block;
				transform: scale(1, 1.3);
			}

			.math-sqrt-stem {
				display: inline-block;
				border-top: 1px solid;
				margin-top: 5px;
			}
		</style>
	</head>
	<body>

		<canvas id="c"></canvas>

		<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - multiple elements with text - webgl</div>

		<script type="module">

			import * as THREE from '../build/three.module.js';

			import { OrbitControls } from './jsm/controls/OrbitControls.js';

			const scenes = [];

			let views, t, canvas, renderer;

			window.onload = init;

			function init() {

				const balls = 20;
				const size = .25;

				const colors = [
					'rgb(0,127,255)', 'rgb(255,0,0)', 'rgb(0,255,0)', 'rgb(0,255,255)',
					'rgb(255,0,255)', 'rgb(255,0,127)', 'rgb(255,255,0)', 'rgb(0,255,127)'
				];

				canvas = document.getElementById( 'c' );

				renderer = new THREE.WebGLRenderer( { canvas: canvas, antialias: true } );
				renderer.setPixelRatio( window.devicePixelRatio );

				views = document.querySelectorAll( '.view' );

				for ( let n = 0; n < views.length; n ++ ) {

					const scene = new THREE.Scene();
					scene.background = new THREE.Color( 0xffffff );

					const geometry0 = new THREE.BufferGeometry();
					const geometry1 = new THREE.BufferGeometry();

					const vertices = [];

					if ( views[ n ].lattice ) {

						const range = balls / 2;
						for ( let i = - range; i <= range; i ++ ) {

							for ( let j = - range; j <= range; j ++ ) {

								for ( let k = - range; k <= range; k ++ ) {

									vertices.push( i, j, k );

								}

							}

						}

					} else {

						for ( let m = 0; m < Math.pow( balls, 3 ); m ++ ) {

							const i = balls * Math.random() - balls / 2;
							const j = balls * Math.random() - balls / 2;
							const k = balls * Math.random() - balls / 2;

							vertices.push( i, j, k );

						}

					}

					geometry0.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
					geometry1.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices.slice(), 3 ) );

					const index = Math.floor( colors.length * Math.random() );

					const canvas2 = document.createElement( 'canvas' );
					canvas2.width = 128;
					canvas2.height = 128;
					const context = canvas2.getContext( '2d' );
					context.arc( 64, 64, 64, 0, 2 * Math.PI );
					context.fillStyle = colors[ index ];
					context.fill();
					const texture = new THREE.CanvasTexture( canvas2 );

					const material = new THREE.PointsMaterial( { size: size, map: texture, transparent: true, alphaTest: 0.1 } );

					scene.add( new THREE.Points( geometry0, material ) );

					scene.userData.view = views[ n ];
					scene.userData.geometry1 = geometry1;

					const camera = new THREE.PerspectiveCamera( 75, 1, 0.1, 100 );
					camera.position.set( 0, 0, 1.2 * balls );
					scene.userData.camera = camera;

					const controls = new OrbitControls( camera, views[ n ] );
					scene.userData.controls = controls;

					scenes.push( scene );

				}

				t = 0;
				animate();

			}

			function updateSize() {

				const width = canvas.clientWidth;
				const height = canvas.clientHeight;

				if ( canvas.width !== width || canvas.height != height ) {

					renderer.setSize( width, height, false );

				}

			}

			function animate() {

				render();
				requestAnimationFrame( animate );

			}

			function render() {

				updateSize();

				renderer.setClearColor( 0xffffff );
				renderer.setScissorTest( false );
				renderer.clear();

				renderer.setClearColor( 0x000000 );
				renderer.setScissorTest( true );

				scenes.forEach( function ( scene ) {

					const rect = scene.userData.view.getBoundingClientRect();

					// check if it's offscreen. If so skip it

					if ( rect.bottom < 0 || rect.top > renderer.domElement.clientHeight ||
						 rect.right < 0 || rect.left > renderer.domElement.clientWidth ) {

						return; // it's off screen

					}

					// set the viewport

					const width = rect.right - rect.left;
					const height = rect.bottom - rect.top;
					const left = rect.left;
					const bottom = renderer.domElement.clientHeight - rect.bottom;

					renderer.setViewport( left, bottom, width, height );
					renderer.setScissor( left, bottom, width, height );

					renderer.render( scene, scene.userData.camera );

					const points = scene.children[ 0 ];
					const position = points.geometry.attributes.position;

					const point = new THREE.Vector3();
					const offset = new THREE.Vector3();

					for ( let i = 0; i < position.count; i ++ ) {

						point.fromBufferAttribute( scene.userData.geometry1.attributes.position, i );

						scene.userData.view.displacement( point.x, point.y, point.z, t / 5, offset );

						position.setXYZ( i, point.x + offset.x, point.y + offset.y, point.z + offset.z );

					}

					position.needsUpdate = true;

				} );

				t ++;

			}

		</script>

		<p>Sound waves whose geometry is determined by a single dimension, plane waves, obey the wave equation</p>

		<!-- css math formatting inspired by http://mathquill.com/mathquill/mathquill.css -->

		<div class="math">

			<span class="math-frac">
				<span class="math-num">
					&part;<sup>2</sup><i>u</i>
				</span>
				<span class="math-denom">
					&part;<i>r</i><sup>2</sup>
				</span>
			</span>

			&minus;

			<span class="math-frac">
				<span class="math-num">
					1<sup></sup> <!-- sup for vertical alignment -->
				</span>
				<span class="math-denom">
					<i>c</i><sup>2</sup>
				</span>
			</span>

			<span class="math-frac">
				<span class="math-num">
					&part;<sup>2</sup><i>u</i>
				</span>
				<span class="math-denom">
					&part;<i>t</i><sup>2</sup>
				</span>
			</span>

			=&nbsp;0

		</div>

		<p>where <i>c</i> designates the speed of sound in the medium. The monochromatic solution for plane waves will be taken to be</p>

		<div class="math">

			<i>u</i>(<i>r</i>,<i>t</i>)&thinsp;=&nbsp;sin(<i>k</i><i>r</i>&thinsp;&plusmn;&thinsp;&omega;<i>t</i>)

		</div>

		<p>where &omega; is the frequency and <i>k</i>=&omega;/<i>c</i> is the wave number. The sign chosen in the argument determines the direction of movement of the waves.</p>

		<p>Here is a plane wave moving on a three-dimensional lattice of atoms:</p>

		<div class="view">

		<script>

			let parent = document.scripts[ document.scripts.length - 1 ].parentNode;

			parent.displacement = function ( x, y, z, t, target ) {

				return target.set( Math.sin( x - t ), 0, 0 );

			};

			parent.lattice = true;

		</script>

		</div>

		<p>Here is a plane wave moving through a three-dimensional random distribution of molecules:</p>

		<div class="view">

		<script>

			parent = document.scripts[ document.scripts.length - 1 ].parentNode;

			parent.displacement = function ( x, y, z, t, target ) {

				return target.set( Math.sin( x - t ), 0, 0 );

			};

			parent.lattice = false;

		</script>

		</div>

		<p>Sound waves whose geometry is determined by two dimensions, cylindrical waves, obey the wave equation</p>

		<div class="math">

			<span class="math-frac">
				<span class="math-num">
					&part;<sup>2</sup><i>u</i>
				</span>
				<span class="math-denom">
					&part;<i>r</i><sup>2</sup>
				</span>
			</span>

			&plus;

			<span class="math-frac">
				<span class="math-num">
					1
				</span>
				<span class="math-denom">
					<i>r</i>
				</span>
			</span>

			<span class="math-frac">
				<span class="math-num">
					&part;<i>u</i>
				</span>
				<span class="math-denom">
					&part;<i>r</i>
				</span>
			</span>

			&minus;

			<span class="math-frac">
				<span class="math-num">
					1<sup></sup> <!-- sup for vertical alignment -->
				</span>
				<span class="math-denom">
					<i>c</i><sup>2</sup>
				</span>
			</span>

			<span class="math-frac">
				<span class="math-num">
					&part;<sup>2</sup><i>u</i>
				</span>
				<span class="math-denom">
					&part;<i>t</i><sup>2</sup>
				</span>
			</span>

			=&nbsp;0

		</div>

		<p>The monochromatic solution for cylindrical sound waves will be taken to be</p>

		<div class="math">

			<i>u</i>(<i>r</i>,<i>t</i>)&thinsp;=

			<span class="math-frac">
				<span class="math-num">
					sin(<i>k</i><i>r</i>&thinsp;&plusmn;&thinsp;&omega;<i>t</i>)
				</span>
				<span class="math-denom">
					<span class="math-sqrt">&radic;</span><span class="math-sqrt-stem"><i>r</i></span>
				</span>
			</span>

		</div>

		<p>Here is a cylindrical wave moving on a three-dimensional lattice of atoms:</p>

		<div class="view">

		<script>

			parent = document.scripts[ document.scripts.length - 1 ].parentNode;

			parent.displacement = function ( x, y, z, t, target ) {

				if ( x * x + y * y < 0.01 ) {

					return target.set( 0, 0, 0 );

				} else {

					const rho = Math.sqrt( x * x + y * y );
					const phi = Math.atan2( y, x );

					return target.set( 1.5 * Math.cos( phi ) * Math.sin( rho - t ) / Math.sqrt( rho ), 1.5 * Math.sin( phi ) * Math.sin( rho - t ) / Math.sqrt( rho ), 0 );

				}

			};

			parent.lattice = true;

		</script>

		</div>

		<p>Here is a cylindrical wave moving through a three-dimensional random distribution of molecules:</p>

		<div class="view">

		<script>

			parent = document.scripts[ document.scripts.length - 1 ].parentNode;

			parent.displacement = function ( x, y, z, t, target ) {

				if ( x * x + y * y < 0.01 ) {

					return target.set( 0, 0, 0 );

				} else {

					const rho = Math.sqrt( x * x + y * y );
					const phi = Math.atan2( y, x );

					return target.set( 1.5 * Math.cos( phi ) * Math.sin( rho - t ) / Math.sqrt( rho ), 1.5 * Math.sin( phi ) * Math.sin( rho - t ) / Math.sqrt( rho ), 0 );

				}

			};

			parent.lattice = false;

		</script>

		</div>

		<p>Sound waves whose geometry is determined by three dimensions, spherical waves, obey the wave equation</p>

		<div class="math">

			<span class="math-frac">
				<span class="math-num">
					&part;<sup>2</sup><i>u</i>
				</span>
				<span class="math-denom">
					&part;<i>r</i><sup>2</sup>
				</span>
			</span>

			&plus;

			<span class="math-frac">
				<span class="math-num">
					2
				</span>
				<span class="math-denom">
					<i>r</i>
				</span>
			</span>

			<span class="math-frac">
				<span class="math-num">
					&part;<i>u</i>
				</span>
				<span class="math-denom">
					&part;<i>r</i>
				</span>
			</span>

			&minus;

			<span class="math-frac">
				<span class="math-num">
					1<sup></sup> <!-- sup for vertical alignment -->
				</span>
				<span class="math-denom">
					<i>c</i><sup>2</sup>
				</span>
			</span>

			<span class="math-frac">
				<span class="math-num">
					&part;<sup>2</sup><i>u</i>
				</span>
				<span class="math-denom">
					&part;<i>t</i><sup>2</sup>
				</span>
			</span>

			=&nbsp;0

		</div>

		<p>The monochromatic solution for spherical sound waves will be taken to be</p>

		<div class="math">

			<i>u</i>(<i>r</i>,<i>t</i>)&thinsp;=

			<span class="math-frac">
				<span class="math-num">
					sin(<i>k</i><i>r</i>&thinsp;&plusmn;&thinsp;&omega;<i>t</i>)
				</span>
				<span class="math-denom">
					<i>r</i>
				</span>
			</span>

		</div>

		<p>Here is a spherical wave moving on a three-dimensional lattice of atoms:</p>

		<div class="view">

		<script>

			parent = document.scripts[ document.scripts.length - 1 ].parentNode;

			parent.displacement = function ( x, y, z, t, target ) {

				if ( x * x + y * y + z * z < 0.01 ) {

					return target.set( 0, 0, 0 );

				} else {

					const r = Math.sqrt( x * x + y * y + z * z );
					const theta = Math.acos( z / r );
					const phi = Math.atan2( y, x );

					return target.set( 3 * Math.cos( phi ) * Math.sin( theta ) * Math.sin( r - t ) / r, 3 * Math.sin( phi ) * Math.sin( theta ) * Math.sin( r - t ) / r, 3 * Math.cos( theta ) * Math.sin( r - t ) / r );

				}

			};

			parent.lattice = true;

		</script>

		</div>

		<p>Here is a spherical wave moving through a three-dimensional random distribution of molecules:</p>

		<div class="view">

		<script>

			parent = document.scripts[ document.scripts.length - 1 ].parentNode;

			parent.displacement = function ( x, y, z, t, target ) {

				if ( x * x + y * y + z * z < 0.01 ) {

					return target.set( 0, 0, 0 );

				} else {

					const r = Math.sqrt( x * x + y * y + z * z );
					const theta = Math.acos( z / r );
					const phi = Math.atan2( y, x );

					return target.set( 3 * Math.cos( phi ) * Math.sin( theta ) * Math.sin( r - t ) / r, 3 * Math.sin( phi ) * Math.sin( theta ) * Math.sin( r - t ) / r, 3 * Math.cos( theta ) * Math.sin( r - t ) / r );

				}

			};

			parent.lattice = false;

		</script>

		</div>

		<p>The mathematical description of sound waves can be carried to higher dimensions, but one needs to wait for Four.js and its higher-dimensional successors to attempt visualizations.</p>

	</body>
</html>
